package cn.bbstone.pisces2.server.fli;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.spi.LocaleServiceProvider;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.bbstone.pisces2.comm.Const;
import cn.bbstone.pisces2.comm.fli.FliSavePoint;
import cn.bbstone.pisces2.server.cache.ServerCache;
import cn.bbstone.pisces2.util.BFileUtil;
import cn.bbstone.pisces2.util.CtxUtil;
import cn.bbstone.pisces2.util.FLIUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;

/**
 * fli.idx with fields(separator: -|:|-)
 * flieNo-|:|-fileCat-|:|-checksum-|:|-rpath
 * <p>
 * will ignore file which is symbol links
 */
public class FliBuilder {
	private static Logger log = LoggerFactory.getLogger(FliBuilder.class);

	// write cache
	public static final int batchSize = 8 * 1024;
	private static int count = 0;
	private static int batch = 0;

	/**
	 * directly write index item to default fli.idx file(~/.fli_server/fli.idx)
	 *
	 * @param rootPath
	 * @param filter
	 * @return - fli.idx total lines number
	 */
	public static int buildServerFliIndex(String rootPath, List<String> filter) {
		long start = System.currentTimeMillis();
		if (Files.notExists(Paths.get(rootPath))) {
			log.warn("not found file/directory: {}", rootPath);
		}

		// delete file list index file first
		try {
			Files.delete(Paths.get(FLIUtil.getServerIndexPath()));
		} catch (IOException e) {
			log.error("delete server fli index file error(before update it)", e);
		}
		// reset file list index counter
		count = 0;
		batch = 0;
		List<String> idxItemList = new ArrayList<>(batchSize);
		// do build process
		try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(FLIUtil.getServerIndexPath()),
				StandardCharsets.UTF_8, StandardOpenOption.APPEND)) {
			File file = new File(rootPath);
			if (Files.isDirectory(Paths.get(rootPath))) {
				// do recursively
				buildIndex(file, filter, writer, idxItemList);
			} else { // specified server root path is file (not directory), this is unexpected(should
						// not happen).
				String relativePath = FLIUtil.getRelativePath(file.getAbsolutePath(), BFileUtil.getServerDir());
				if (StrUtil.isBlank(relativePath)) {
					log.warn("skip file: {}", file.getAbsolutePath());
				}
				if (StrUtil.isNotBlank(relativePath) && !isFilter(relativePath)) {
					count++;
					// line data like: lineNum,D/F,/workdir/test/pisces/bin/client.bat
					writer.write(String.format("%d,%s,%s", count, determineFileCat(file), relativePath));
					writer.newLine();
				}
			}
			// the last traverse fli items amount not meet batch size, should write to
			// fli.idx too.
			writeServerIndexCache(writer, idxItemList);
		} catch (IOException ioe) {
			log.error("build server fli index file error", ioe);
		}
		//
		long idxEnd = System.currentTimeMillis();
		log.debug("server index file updated, cost: {} ms.", (idxEnd - start));
		FliSavePoint fliSavePoint = new FliSavePoint();
		fliSavePoint.setCount(count);
		fliSavePoint.setChecksum(BFileUtil.checksum(FLIUtil.getServerIndexPath()));
		fliSavePoint.setFileNo(-1); // field in server fli.sp invalid
		fliSavePoint.setUpdateTime(System.currentTimeMillis());
		FLIUtil.writeServerSavePoint(fliSavePoint);
		// cache save point info
		ServerCache.setFliSavePoint(fliSavePoint);
		//
		log.debug("server savepoint file updated, cost: {} ms.", (System.currentTimeMillis() - idxEnd));
		log.debug("build server fli index total cost: {} ms.", (System.currentTimeMillis() - start));
		return count;
	}

	private static void buildIndex(File file, List<String> filter, BufferedWriter writer, List<String> idxItemList)
			throws IOException {
		File[] flist = file.listFiles();
		if (flist == null) {
			log.info("can't list file: {}", file.getAbsolutePath());
			return;
		}
		Arrays.sort(flist);
		for (File subfile : flist) {
			String relativePath = FLIUtil.getRelativePath(subfile.getAbsolutePath(), BFileUtil.getServerDir());
			if (StrUtil.isBlank(relativePath)) {
				log.warn("skip file: {}", file.getAbsolutePath());
			}
			if (StrUtil.isNotBlank(relativePath) && !isFilter(relativePath)) {
				count++;
				idxItemList.add(String.format("%d%s%s%s%s", count, Const.FLI_FIELD_SEPARATOR, determineFileCat(subfile),
						Const.FLI_FIELD_SEPARATOR, relativePath));
				if (idxItemList.size() > 0 && idxItemList.size() % batchSize == 0) {
					writeServerIndexCache(writer, idxItemList);
				}
			}

			if (subfile.isDirectory()) {
				buildIndex(subfile, filter, writer, idxItemList);
			}
		}
	}

	private static String determineFileCat(File file) {
		if (file.isDirectory())
			return Const.BFILE_CAT_DIR;
		if (file.isFile())
			return Const.BFILE_CAT_FILE;
		if (Files.isSymbolicLink(Paths.get(file.getAbsolutePath()))) {
			return Const.BFILE_CAT_SL;
		}
		return null;
	}

	private static void writeServerIndexCache(BufferedWriter writer, List<String> idxItemList) throws IOException {
		for (int i = 0; i < idxItemList.size(); i++) {
			writer.write(idxItemList.get(i));
			writer.newLine();
//            count++;
		}
		// clear cache
		if (idxItemList.size() > 0)
			batch++;
		idxItemList.clear();
		log.info("build index progress, wrote: {} batch, total: {} line.", batch, count);
	}

	// ------------------------ fli index ---------
	private static boolean isFilter(String rpath) {
		List<String> filterList = getFliFilter();
		if (CollUtil.isNotEmpty(filterList)) {
			for (String regexp : filterList) {
				// find rpath not match filter rules, keep it
				if (ReUtil.contains(regexp, rpath))
					return true;
			}
		}
		return false;
	}

	public static List<String> getFliFilter() {
		String fliFilter = CtxUtil.getConfigModel().getFilter();
		if (fliFilter == null || fliFilter.isEmpty()) {
			return null;
		}
		return StrUtil.splitTrim(fliFilter, Const.COMMA_STR);
	}

	public static void main(String[] args) {
//        List<String> filter = Arrays.asList(".DS_Store", ".mvn");
//        int size = buildServerFliIndex(BFileUtil.getServerDir(), filter);

//        String path = "/Users/bbstone/workdir/assets/assets_pro/neomind/blessing-deploy/bless";
//        log.info("fileType: {}", determineFileCat(new File(path)));

	}

}
